package pure {	
	import flash.display.*;
	import flash.events.*;
	import flash.geom.*;

public class BlitMask extends Sprite 
{
 
	//protected static var _tempContainer:Sprite = new Sprite();
	protected static var _sliceRect:Rectangle = new Rectangle();    // 窗口矩形
	protected static var _destPoint:Point = new Point();
	protected static var _tempMatrix:Matrix = new Matrix();
	protected static var _emptyArray:Array = [];
	protected static var _colorTransform:ColorTransform = new ColorTransform();
	protected static var _mouseEvents:Array = [MouseEvent.CLICK,
										MouseEvent.DOUBLE_CLICK,
										MouseEvent.MOUSE_DOWN, 
										MouseEvent.MOUSE_MOVE,
										MouseEvent.MOUSE_OUT, 
										MouseEvent.MOUSE_OVER, 
										MouseEvent.MOUSE_UP, 
										MouseEvent.MOUSE_WHEEL, 
										MouseEvent.ROLL_OUT, 
										MouseEvent.ROLL_OVER];
										
	protected var _target:DisplayObject;
	protected var _fillColor:uint;
	protected var _smoothing:Boolean;
	protected var _width:Number;
	protected var _height:Number;
	protected var _bd:BitmapData;

	protected var _gridSize:int = 2879;
	protected var _grid:Array;
	protected var _bounds:Rectangle;    // 显示对象矩形
	protected var _clipRect:Rectangle;    // 总区域
	protected var _bitmapMode:Boolean;
	
	protected var _rows:int;
	protected var _columns:int;
	protected var _scaleX:Number;
	protected var _scaleY:Number;
	protected var _prevMatrix:Matrix;
	protected var _transform:Transform;
	protected var _prevRotation:Number;
	protected var _autoUpdate:Boolean;
	protected var _wrap:Boolean;
	protected var _wrapOffsetX:Number = 0;
	protected var _wrapOffsetY:Number = 0;
	

	public function BlitMask(target:DisplayObject, x:Number = 0, y:Number = 0, width:Number = 100, height:Number = 100, smoothing:Boolean = false, autoUpdate:Boolean = false, fillColor:uint = 0x00000000, wrap:Boolean = false)
	{
		if (width < 0 || height < 0) {
			throw new Error("A FlexBlitMask cannot have a negative width or height.");
		}
		_width = width;
		_height = height;
		_scaleX = _scaleY = 1;
		_smoothing = smoothing;
		_fillColor = fillColor;
		_autoUpdate = autoUpdate;
		_wrap = wrap;
		_grid = [];
		_bounds = new Rectangle();
		if (_smoothing) {
			super.x = x;
			super.y = y;
		} else { 
			super.x = (x < 0) ? (x - 0.5) >> 0 : (x + 0.5) >> 0;
			super.y = (y < 0) ? (y - 0.5) >> 0 : (y + 0.5) >> 0;
		}
		_clipRect = new Rectangle(0, 0, _gridSize + 1, _gridSize + 1);
		_bd = new BitmapData(width + 1, height + 1, true, _fillColor);
		_bitmapMode = true;
		
		this.target = target;
	}

	public function update(event:Event = null, forceRecaptureBitmap:Boolean = false):void 
	{
		if (_bd == null) return;
		
		else if (_target == null) _render();
		else if (_target.parent) {
			_bounds = _target.getBounds(_target.parent);
			if (this.parent != _target.parent) {
				_target.parent.addChildAt(this, _target.parent.getChildIndex(_target));
			}
		}
		if (_bitmapMode || forceRecaptureBitmap) {
			var m:Matrix = _transform.matrix;
			if (forceRecaptureBitmap || _prevMatrix == null || m.a != _prevMatrix.a || m.b != _prevMatrix.b || m.c != _prevMatrix.c || m.d != _prevMatrix.d) {
				_captureTargetBitmap();
				_render();
			} else if (m.tx != _prevMatrix.tx || m.ty != _prevMatrix.ty) {
				_render();
			} else if (_bitmapMode && _target != null) {
				this.filters = _target.filters;
				this.transform.colorTransform = _transform.colorTransform;
			}
			_prevMatrix = m;
		}
	}

	public function normalizePosition():void
	{
		if (_target && _bounds) {
			var wrapWidth:int = (_bounds.width + _wrapOffsetX + 0.5) >> 0;
			var wrapHeight:int = (_bounds.height + _wrapOffsetY + 0.5) >> 0;
			var offsetX:Number = (_bounds.x - this.x) % wrapWidth;
			var offsetY:Number = (_bounds.y - this.y) % wrapHeight;
			
			if (offsetX > (_width + _wrapOffsetX) / 2) {
				offsetX -= wrapWidth;
			} else if (offsetX < (_width + _wrapOffsetX) / -2) {
				offsetX += wrapWidth;
			}
			if (offsetY > (_height + _wrapOffsetY) / 2) {
				offsetY -= wrapHeight;
			} else if (offsetY < (_height + _wrapOffsetY) / -2) {
				offsetY += wrapHeight;
			}
			
			_target.x += this.x + offsetX - _bounds.x;
			_target.y += this.y + offsetY - _bounds.y;
		}
	}




	/* ----------------------------------------------------    get / set    ----------------------------------------------------*/




	public function get bitmapMode():Boolean { return _bitmapMode; }
	public function set bitmapMode(value:Boolean):void {
		if (_bitmapMode != value) {
			_bitmapMode = value;
			if (_target != null) {
				_target.visible = !_bitmapMode;
				update(null);
				if (_bitmapMode) {
					this.filters = _target.filters;
					this.transform.colorTransform = _transform.colorTransform;
					this.blendMode = _target.blendMode;
					_target.mask = null;
					
				//
				} else {
					this.filters = _emptyArray;
					this.transform.colorTransform = _colorTransform;
					this.blendMode = "normal";
					this.cacheAsBitmap = false;
					_target.mask = this;
					if (_wrap) {
						normalizePosition();
					}
				}
				if (_bitmapMode && _autoUpdate) {
					this.addEventListener(Event.ENTER_FRAME, update, false, -10, true);
				} else {
					this.removeEventListener(Event.ENTER_FRAME, update);
				}
			}
		}
	}

	public function get autoUpdate():Boolean { return _autoUpdate; }
	public function set autoUpdate(value:Boolean):void {
		if (_autoUpdate != value) {
			_autoUpdate = value;
			if (_bitmapMode && _autoUpdate) {
				this.addEventListener(Event.ENTER_FRAME, update, false, -10, true);
			} else {
				this.removeEventListener(Event.ENTER_FRAME, update);
			}
		}
	}
	
	/** The target DisplayObject that the BlitMask should mask **/
	public function get target():DisplayObject { return _target; }
	public function set target(value:DisplayObject):void {
		if (_target != value) {
			var i:int = _mouseEvents.length;
			if (_target != null) {
				while (--i > -1) {
					_target.removeEventListener(_mouseEvents[i], _mouseEventPassthrough);
				}
			}
			_target = value;
			if (_target != null) {
				i = _mouseEvents.length;
				while (--i > -1) {
					_target.addEventListener(_mouseEvents[i], _mouseEventPassthrough, false, 0, true);
				}
				_prevMatrix = null;
				_transform = _target.transform;
				_bitmapMode = !_bitmapMode; 
				this.bitmapMode = !_bitmapMode; //forces a refresh (applying the mask, doing an update(), etc.)
			} else {
				_bounds = new Rectangle();
			}
		}
	}
	
	/** x coordinate of the BlitMask (it will automatically be forced to whole pixel values if <code>smoothing</code> is <code>false</code>). **/
	override public function get x():Number { return super.x; }
	override public function set x(value:Number):void {
		if (_smoothing) {
			super.x = value;
		} else if (value >= 0) {
			super.x = (value + 0.5) >> 0;
		} else {
			super.x = (value - 0.5) >> 0;
		}
		if (_bitmapMode) {
			_render();
		}
	}
	
	/** y coordinate of the BlitMask (it will automatically be forced to whole pixel values if <code>smoothing</code> is <code>false</code>). **/
	override public function get y():Number { return super.y; }
	override public function set y(value:Number):void {
		if (_smoothing) {
			super.y = value;
		} else if (value >= 0) {
			super.y = (value + 0.5) >> 0;
		} else {
			super.y = (value - 0.5) >> 0;
		}
		if (_bitmapMode) {
			_render();
		}
	}

	public function get scrollX():Number
	{
		return (super.x - _bounds.x) / (_bounds.width - _width);
	}
	
	public function set scrollX(value:Number):void 
	{
		if (_target != null && _target.parent) {
			_bounds = _target.getBounds(_target.parent);
			var dif:Number;
			dif = (super.x - (_bounds.width - _width) * value) - _bounds.x;
			_target.x += dif;
			_bounds.x += dif;
			if (_bitmapMode) {
				_render();
			}
		}
	}

	public function get scrollY():Number
	{
		return (super.y - _bounds.y) / (_bounds.height - _height);
	}
	
	public function set scrollY(value:Number):void
	{
		if (_target != null && _target.parent) {
			_bounds = _target.getBounds(_target.parent);
			var dif:Number = (super.y - (_bounds.height - _height) * value) - _bounds.y;
			_target.y += dif;
			_bounds.y += dif;
			if (_bitmapMode) {
				_render();
			}
		}
	}

	public function get smoothing():Boolean { return _smoothing; }
	public function set smoothing(value:Boolean):void {
		if (_smoothing != value) {
			_smoothing = value;
			_captureTargetBitmap();
			if (_bitmapMode) _render();
		}
	}

	public function get fillColor():uint { return _fillColor; }
	public function set fillColor(value:uint):void {
		if (_fillColor != value) {
			_fillColor = value;
			if (_bitmapMode) _render();
		}
	}

	public function get wrap():Boolean { return _wrap; }
	public function set wrap(value:Boolean):void {
		if (_wrap != value) {
			_wrap = value;
			if (_bitmapMode) _render();
		}
	}

	public function get wrapOffsetX():Number { return _wrapOffsetX; }
	public function set wrapOffsetX(value:Number):void {
		if (_wrapOffsetX != value) {
			_wrapOffsetX = value;
			if (_bitmapMode) _render();
		}
	}

	public function get wrapOffsetY():Number { return _wrapOffsetY; }
	public function set wrapOffsetY(value:Number):void {
		if (_wrapOffsetY != value) {
			_wrapOffsetY = value;
			if (_bitmapMode) {
				_render();
			}
		}
	}



	/* ----------------------------------------------------    private    ----------------------------------------------------*/
	
	

	protected function _render(xOffset:Number = 0, yOffset:Number = 0, clear:Boolean = true, limitRecursion:Boolean = false):void
	{
		if (clear) {
			_sliceRect.x = _sliceRect.y = 0;
			_sliceRect.width = _width + 1;
			_sliceRect.height = _height + 1;
			_bd.fillRect(_sliceRect, _fillColor);
			
			if (_bitmapMode && _target != null) {
				this.filters = _target.filters;
				this.transform.colorTransform = _transform.colorTransform;
			} else {
				this.filters = _emptyArray;
				this.transform.colorTransform = _colorTransform;
			}
		}
		
		// distroyed
		if (_bd == null) return;
		
		// 新捕获
		else if (_rows == 0) _captureTargetBitmap();
		
		var x:Number = super.x + xOffset;
		var y:Number = super.y + yOffset;
		
		
		var wrapWidth:int = (_bounds.width + _wrapOffsetX + 0.5) >> 0;
		var wrapHeight:int = (_bounds.height + _wrapOffsetY + 0.5) >> 0;
		var g:Graphics = this.graphics;
		
		// 
		if (_bounds.width == 0 || _bounds.height == 0 || (_wrap && (wrapWidth == 0 || wrapHeight == 0)) || (!_wrap && (x + _width < _bounds.x || y + _height < _bounds.y || x > _bounds.right || y > _bounds.bottom))) {
			g.clear();
			g.beginBitmapFill(_bd);
			g.drawRect(0, 0, _width, _height);
			g.endFill();
			return;
		}
		
		var column:int = int((x - _bounds.x) / _gridSize);
		if (column < 0) { column = 0; }
		
		var row:int = int((y - _bounds.y) / _gridSize);
		if (row < 0) { row = 0; }
		
		var maxColumn:int = int(((x + _width) - _bounds.x) / _gridSize);
		if (maxColumn >= _columns) { maxColumn = _columns - 1; }
		
		var maxRow:uint = int(((y + _height) - _bounds.y) / _gridSize);
		if (maxRow >= _rows) { maxRow = _rows - 1; }
		
		var xNudge:Number = (_bounds.x - x) % 1;
		var yNudge:Number = (_bounds.y - y) % 1;
		
		if (y <= _bounds.y) {
			_destPoint.y = (_bounds.y - y) >> 0;
			// 减去1，没有这个，动画边缘会抖动.
			_sliceRect.y = -1; 
			
		} else {
			_destPoint.y = 0;
			// 减去1，没有这个，动画边缘会抖动.
			_sliceRect.y = Math.ceil(y - _bounds.y) - (row * _gridSize) - 1;
			if (clear && yNudge != 0) {
				yNudge += 1;
			}
			
		}
		if (x <= _bounds.x) {
			_destPoint.x = (_bounds.x - x) >> 0;
			// 减去1，没有这个，动画边缘会抖动.
			_sliceRect.x = -1;
			
		} else {
			_destPoint.x = 0;
			// 减去1，没有这个，动画边缘会抖动.
			_sliceRect.x = Math.ceil(x - _bounds.x) - (column * _gridSize) - 1;
			if (clear && xNudge != 0) {
				xNudge += 1;
			}
		}
		
		if (_wrap && clear) {
			//make sure to offset appropriately so that we start drawing directly on the image. We must use consistent xNudge and yNudge values across all the recursive calls too, otherwise the copies may vibrate visually a bit as they move
			_render(Math.ceil((_bounds.x - x) / wrapWidth) * wrapWidth, Math.ceil((_bounds.y - y) / wrapHeight) * wrapHeight, false, false);
		} else if (_rows != 0) {
			var xDestReset:Number = _destPoint.x;
			var xSliceReset:Number = _sliceRect.x;
			var columnReset:int = column;
			var bd:BitmapData;
			while (row <= maxRow) {
				bd = _grid[row][0];
				_sliceRect.height = bd.height - _sliceRect.y;
				_destPoint.x = xDestReset;
				_sliceRect.x = xSliceReset;
				column = columnReset;
				while (column <= maxColumn) {
					bd = _grid[row][column];
					_sliceRect.width = bd.width - _sliceRect.x;
					
					_bd.copyPixels(bd, _sliceRect, _destPoint);
					
					_destPoint.x += _sliceRect.width - 1;
					_sliceRect.x = 0;
					column++;
				}
				_destPoint.y += _sliceRect.height - 1;
				_sliceRect.y = 0;
				row++;
			}
			
		}
		
		if (clear) {
			_tempMatrix.tx = xNudge - 1; //subtract 1 to compensate for the pixel we added above.
			_tempMatrix.ty = yNudge - 1;
			g.clear();
			g.beginBitmapFill(_bd, _tempMatrix, false, _smoothing);
			g.drawRect(0, 0, _width, _height);
			g.endFill();
		} else if (_wrap) {
			// 递归调用_render()并调整offset(s)补全位图
			if (x + _width > _bounds.right) {
				_render(xOffset - wrapWidth, yOffset, false, true);
			} 
			if (!limitRecursion && y + _height > _bounds.bottom) {
				_render(xOffset, yOffset - wrapHeight, false, false);
			}
		}
	}
	
	protected function _captureTargetBitmap():void
	{
		if (_bd == null || _target == null) return;
		
		_disposeGrid();
		
		var prevMask:DisplayObject = _target.mask;
		if (prevMask != null) { _target.mask = null; }
		
		var prevScrollRect:Rectangle = _target.scrollRect;
		if (prevScrollRect != null) { _target.scrollRect = null; }
		
		var prevFilters:Array = _target.filters;
		if (prevFilters.length != 0) { _target.filters = _emptyArray; }
		
		_grid = [];
		
		//if (_target.parent == null) {
			//_tempContainer.addChild(_target);
		//}
		_bounds = _target.getBounds(_target);
		var w:Number = 0;
		var h:Number = 0;
		_columns = Math.ceil(_bounds.width / _gridSize);
		_rows = Math.ceil(_bounds.height / _gridSize);
		
		var matrix:Matrix = _transform.matrix;
		var xOffset:Number = matrix.tx - _bounds.x;
		var yOffset:Number = matrix.ty - _bounds.y;
		if (!_smoothing) {
			xOffset = (xOffset + 0.5) >> 0;
			yOffset = (yOffset + 0.5) >> 0;
		}
		
		var bd:BitmapData;
		var cumulativeWidth:Number;
		var cumulativeHeight:Number = 0;
		
		for (var row:int = 0; row < _rows; row++) {
			h = (_bounds.height - cumulativeHeight > _gridSize) ? _gridSize : _bounds.height - cumulativeHeight;
			matrix.ty = -cumulativeHeight + yOffset;
			cumulativeWidth = 0;
			_grid[row] = [];
			for (var column:int = 0; column < _columns; column++) {
				w = (_bounds.width - cumulativeWidth > _gridSize) ? _gridSize : _bounds.width - cumulativeWidth;
				_grid[row][column] = bd = new BitmapData(w + 1, h + 1, true, _fillColor);
				matrix.tx = -cumulativeWidth + xOffset;
				bd.draw(_target, matrix, null, null, _clipRect, _smoothing);
				cumulativeWidth += w;
			}
			cumulativeHeight += h;
		}
		
		//if (_target.parent == _tempContainer) { _tempContainer.removeChild(_target); }
		
		if (prevMask != null) { _target.mask = prevMask; }
		if (prevScrollRect != null) { _target.scrollRect = prevScrollRect; }
		if (prevFilters.length != 0) { _target.filters = prevFilters; }
	}
	
	protected function _disposeGrid():void 
	{
		var i:int = _grid.length, j:int, r:Array;
		while (--i > -1) {
			r = _grid[i];
			j = r.length;
			while (--j > -1) {
				BitmapData(r[j]).dispose();
			}
		}
	}

	protected function _mouseEventPassthrough(event:MouseEvent):void
	{
		if (this.mouseEnabled && (!_bitmapMode || this.hitTestPoint(event.stageX, event.stageY, false))) {
			dispatchEvent(event);
		}
	}
}
}